﻿/*----------------------------------------------------------------
// 公司名称：公司名称
//
// 产品名称：Gaf
//
// 创 建 人：netcasewqs
// 创建日期：2022/3/4
// 邮    箱：netcasewqs@qq.com
//
// Copyright (C) 2022 Gaf，保留所有权利。
//
//----------------------------------------------------------------*/

namespace Gaf.Impl;

internal class AddinActivator : IAddinActivator
{
    internal GafImpl _framework = null!;
    internal ClassLoader _classLoader = null!;
    private IAddinActivator _innerActivator = null!;


    internal Addin Addin { get; private set; }

    internal IAddinActivator InnerActivator
    {
        get => _innerActivator;
        set
        {
            _innerActivator = value;
            if (value is IDelegateActivator activator && activator != null)
            {
                var methods = activator.Instance.GetType().GetTypeInfo().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
                HandleAddinStateChanged(methods);
                HandleFrameworkStateChanged(methods);
                HandleFrameworkErrorOccurred(methods);

                void HandleAddinStateChanged(MethodInfo[] methods)
                {
                    var method = (from m in methods.Where(p => p.Name == nameof(IAddinListener.OnAddinStateChanged))
                                  let ps = m.GetParameters()
                                  where ps.Length == 1
                                  where ps[0].ParameterType == typeof(IAddin) //addinID is int 
                                  select m).FirstOrDefault();

                 

                    if (method != null)
                    {
                        var fastMethod = method.IsStatic ? (Action<IAddin>)Delegate.CreateDelegate(typeof(Action<IAddin>), method) : default;

                        _framework.AddinStateChanged += addin =>
                        {
                            try
                            {
                              
                                if (fastMethod != null) fastMethod(addin);
                                else
                                {
                                    if (activator.Instance != null)
                                    {
                                        var args = new object[] { addin };
                                        method.Invoke(activator.Instance, args);
                                    }
                                }
                            }
                            catch (TargetInvocationException)
                            {
                                throw;
                            }
                        };

                        return;
                    }

                    (method, var parameters) = (from m in methods.Where(p => p.Name == nameof(IAddinListener.OnAddinStateChanged))
                                                let ps = m.GetParameters()
                                                where ps.Length is 3 or 4
                                                where ps[0].ParameterType == typeof(int) //addinID is int 
                                                where ps[1].ParameterType == typeof(string) //addinName is string
                                                where IsAddinStateType(ps[2].ParameterType) //addinName is string
                                                select (m, ps)).FirstOrDefault();

                    if (method != null)
                    {
                        if (parameters.Length == 4)
                        {
                            if (!typeof(IEnumerable<Assembly>).IsAssignableFrom(parameters[3].ParameterType)) return;
                        }

                        Delegate? fastMethod = default;
                        if (method.IsStatic)
                        {
                           
                            if(parameters.Length == 3)
                                fastMethod = Delegate.CreateDelegate(typeof(Action<,,>).MakeGenericType(typeof(int), typeof(string), parameters[2].ParameterType), method);
                            else if (parameters.Length == 4)
                                fastMethod = Delegate.CreateDelegate(typeof(Action<,,,>).MakeGenericType(typeof(int), typeof(string), parameters[2].ParameterType, typeof(IEnumerable<Assembly>)), method);
                        }

                        _framework.AddinStateChanged += addin =>
                        {

                            var args = new object[parameters.Length];
                            args[0] = addin.Id;
                            args[1] = addin.Name;
                            args[2] = ConvertAddinState(parameters[2].ParameterType, addin.State);

                            if (args.Length == 4) args[3] = addin.Assemblies;
                            try
                            {

                                if (fastMethod != null)
                                {
                                    fastMethod.DynamicInvoke(args);
                                }
                                else
                                {
                                    if (activator.Instance != null)
                                        method.Invoke(activator.Instance, args);
                                }
                            }
                            catch (TargetInvocationException)
                            {
                                throw;
                            }
                        };
                    }

                }

                void HandleFrameworkStateChanged(MethodInfo[] methods)
                {
                    var (method,ptype)= (from m in methods.Where(p => p.Name == nameof(IAddinListener.OnFrameworkStateChanged))
                                  let ps = m.GetParameters()
                                  where ps.Length == 1
                                  where IsAddinStateType( ps[0].ParameterType ) //addinID is int 
                                  select (m, ps[0].ParameterType)).FirstOrDefault();

                    if (method != null)
                    {
                        _framework.FramewormStateChanged += framework =>
                        {
                            try
                            {
                                var state = ptype.IsEnum ? Enum.ToObject(ptype,(int)framework.State): Convert.ChangeType((int)framework.State,ptype);
                                var args = new object[] { state };

                                if(method.IsStatic) method.Invoke(default, args);
                                else
                                {
                                    if(activator.Instance != null)
                                        method.Invoke(activator.Instance, args);
                                }
                                
                            }
                            catch (TargetInvocationException ex)
                            {
                                throw ex.InnerException!;
                            }
                        };

                        return;
                    }
                }

                void HandleFrameworkErrorOccurred(MethodInfo[] methods)
                {
                    var (method, ptype) = (from m in methods.Where(p => p.Name == nameof(IAddinListener.OnFrameworkErrorOccurred))
                                           let ps = m.GetParameters()
                                           where ps.Length == 1
                                           where typeof(Exception).IsAssignableFrom( ps[0].ParameterType) //addinID is int 
                                           select (m, ps[0].ParameterType)).FirstOrDefault();

                    if (method != null)
                    {
                        //var fastMethod = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(ptype), method);

                        _framework.FrameworkErrorOccurred += (framework,ex) =>
                        {
                            try
                            {
                                var args = new object[] { ex };
                                if (method.IsStatic) method.Invoke(default, args);
                                else
                                {
                                    if (activator.Instance != null)
                                        method.Invoke(activator.Instance, args);
                                }
                            }
                            catch (TargetInvocationException)
                            {
                                throw;
                            }
                        };

                        return;
                    }
                }
            }
        }
    }

    private static object ConvertAddinState(Type type, AddinState state)
    {
        if (type.IsEnum) return Enum.ToObject(type, (int)state);
        var typeCode = Type.GetTypeCode(type);
        return typeCode switch
        {
            TypeCode.Byte => (byte)state,
            TypeCode.SByte => (sbyte)state,
            TypeCode.Int16 => (short)state,
            TypeCode.UInt16 => (ushort)state,
            TypeCode.Int32 => (int)state,
            TypeCode.UInt32 => (uint)state,
            TypeCode.Int64 => (long)state,
            TypeCode.UInt64 => (ulong)state,
            _ => null!,
        };
    }

    internal static bool IsAddinStateType(Type type)
    {
        if (type.IsEnum) return true;
        return Type.GetTypeCode(type) is TypeCode.Byte
            or TypeCode.SByte
            or TypeCode.Int64
            or TypeCode.UInt64
            or TypeCode.UInt32
            or TypeCode.Int32
            or TypeCode.UInt16
            or TypeCode.Int16;
    }
    private interface IAddinListener
    {
        void OnAddinStateChanged(int addinId, string addinName, AddinState addinState);

        void OnFrameworkStateChanged(FrameworkState frameworkState);

        void OnFrameworkErrorOccurred(Exception ex);
    }



    internal AddinActivator(GafImpl framework, AddinDescriptor descriptor, bool isLiteAddin)
    {
        Addin = new Addin
        {
            State = AddinState.Installed,
            Id = IdGenerator.Instance.Next(),
            Location = descriptor.Location,
            Copyright = descriptor.Copyright,
            Description = descriptor.Description,
            Name = descriptor.Name,
            StartLevel = descriptor.StartLevel,
            Vendor = descriptor.Vendor,
            Version = descriptor.Version,
            Manifest = descriptor.Manifest,
            IsLiteAddin = isLiteAddin
        };
        _framework = framework;

        _classLoader = ClassLoader.Create(this);
    }

    public void Configure(IServiceCollection? services, IConfiguration? configuration)
    {
        if (Addin.State != AddinState.Installed)
            throw new AddinException(AddinExceptionType.InstallFailed, $"Addin:'{Addin.Name}' cannot be configue, since it is {Addin.State}.");


        _framework.FireAddinChanged(this, AddinState.Configuring);

        if (InnerActivator != null)
        {
            try
            {
                InnerActivator.Configure(services, configuration);
            }
            catch
            {
                throw;
            }
        }

        _framework.FireAddinChanged(this, AddinState.Configured);
    }

    public void Uninstall(IServiceProvider? serviceProvider)
    {
        //前置检查
        if (Addin.State == AddinState.Uninstalled)
        {
            throw new InvalidOperationException("Cannot uninstall an uninstalled addin:{Descriptor.Name}.");
        }

        if (Addin.State == AddinState.Started)
        {
            try
            {
                Stop(serviceProvider);
            }
            catch 
            {
                
            }
        }

        _framework.FireAddinChanged(this, AddinState.Uninstalling);

        try
        {
            _innerActivator.Uninstall(serviceProvider);
        }
        catch (Exception ex)
        {
            var message = $"addin {Addin.Name} uninstall error,error message:{ex}";
            Log.Error(message, ex);
        }


        try
        {
            _classLoader.Unload();
        }
        catch (Exception ex)
        {
            var message = $"addin {Addin.Name} class loader unload error,error message:{ex}";
            Log.Error(message, ex);
        }

        if (_framework.Options.IsGpk && _framework.Options.AutoDeleteUncompressDirectory)
        {
            try
            {
                Directory.Delete(Addin.Location, true);
            }
            catch
            {
            }
        }

        _framework.FireAddinChanged(this, AddinState.Uninstalled);
        _framework.RemoveAllAddinListeners(this);
    }

    public void Start(IServiceProvider? serviceProvider)
    {
        if (Addin.State == AddinState.Uninstalled)
        {
            throw new InvalidOperationException("Cannot start an uninstalled addin.");
        }
        else if (Addin.State == AddinState.Stopping)
        {
            throw new InvalidOperationException("Addin " + ToString() + " cannot be started, since it is stopping.");
        }
        else if (Addin.State is AddinState.Started or AddinState.Starting)
        {
            return;
        }

        if (Addin.State is AddinState.Configured)
        {
            try
            {
                var result = Match(serviceProvider);
                if (!result)
                    return;
            }
            catch (Exception ex)
            {
                throw new AddinException("Addin " + ToString() + " cannot be started, because addin match error:" + ex.Message, ex);
            }

            if (Addin.State != AddinState.Configured)
                throw new AddinException(AddinExceptionType.InvalidOperation, $"Addin:'{Addin.Name}' cannot be started, since it is {Addin.State}.") { Addin = this.Addin };
        }

        try
        {
            _framework.FireAddinChanged(this, AddinState.Starting);
            InnerActivator?.Start(serviceProvider);
            _framework.FireAddinChanged(this, AddinState.Started);
        }
        catch 
        {
            _framework.FireAddinChanged(this, AddinState.Stopped);
            _framework.RemoveAllAddinListeners(this);
            throw;
        }
    }

    public bool Match(IServiceProvider? serviceProvider)
    {
        try
        {
            _framework.FireAddinChanged(this, AddinState.Matching);
            return InnerActivator.Match(serviceProvider);
        }
        catch (Exception ex)
        {
            _framework.FireAddinChanged(this, AddinState.Configured);
            throw new AddinException(AddinExceptionType.MatchFailed, "Activator match error in addin " + ToString() + ".", ex);
        }
        finally
        {
            _framework.FireAddinChanged(this, AddinState.Matched);
        }
    }

    public void Stop(IServiceProvider? provider)
    {
        switch (Addin.State)
        {
            case AddinState.Uninstalled:
                throw new InvalidOperationException($"Cannot stop an uninstalled addin:{Addin.Name}.");
            case AddinState.Stopping:
                throw new InvalidOperationException($"Stopping a stopping addin:{Addin.Name} is currently not supported.");
            case AddinState.Installed:
            case AddinState.Configuring:
            case AddinState.Configured:
            case AddinState.Matching:
            case AddinState.Matched:
                return;
        }

        _framework.FireAddinChanged(this, AddinState.Stopping);
        try
        {
            InnerActivator.Stop(provider);
        }
        catch (Exception ex)
        {
            var message = $"addin {Addin.Name} stop error,error message:{ex}";
            Log.Error(message,ex);
            throw new AddinException(AddinExceptionType.StopFailed, message, ex);
        }
        finally
        {
            _framework.FireAddinChanged(this, AddinState.Stopped);
            _framework.RemoveAllAddinListeners(this);
        }
    }

    public override string ToString()
    {
        return Addin.ToString();
    }
}